看 react
源码过程中,发现这样的代码
1 | const NoContext = /* */ 0b000000; |
~
、|
和 &
都是「位运算操作符」,平常写代码很少会用到,导致完全不理解这些运算符有什么用,react
源码中这些代码到底是什么意思呢?
~ 运算符
首先来看第一个运算符 ~
,它是所谓的「否定号」或者说「逻辑否」。
与之相对的就是「逻辑与」
&
和「逻辑或」|
。
它的计算过程很简单,就是「二进制取反」,即写出数字的二进制表示,每位都和之前相反,1 变成 0,0 变成 1。
1 | ~1 // ~000001 === 111110 |
它实质上是对数字求负,然后减1。
所以 ~1 === -2
、~0 === -1
这个很好理解,并且我们能得出 -2 的二进制表示正是 111110
1 | const num = -2; |
& 运算符
这个相比 ~
运算符在计算过程上很好理解,但没有 ~
运算符好计算。
1 | 25 & 6; // 0 |
上面代码的计算过程是这样的,把数字写成二进制,上下比较,如果都是 1 那么结果的相同位也是 1 否则就是 0。
1 | 0000 0000 0000 0000 0000 0000 0001 1001 // 25 |
|
运算符
与 &
刚好相反,二进制表示时,上下其中任意一个是 1 那么结果的相同位上也是 1。所以之前的例子
1 | 0000 0000 0000 0000 0000 0000 0001 1001 // 25 |
和 &
一样无法直观地看出结果。
react 中的位运算
在了解三个运算符后,我们回过头看看 react
源码,它有多种用法
1 | let executionContext = NoContext; |
这里每个常量的值都是有目的的,分别为 0、1、2、4、8、16、32,而不是像枚举那样 0、1、2、3,就是为了配合位运算。
1 | const NoContext = /* */ 0b000000; // 0 |
那么这些特定的数字是怎么配合位运算做到「有含义」的呢?
其实就是 executionContext
所对应二进制中 1 的位置,记录了它所「包含」的 xxxContext
,举例来说 000011
表示 BatchedContext
和 EventContext
;110001
表示 CommitContext
和 RenderContext
和 BatchedContext
;
表示状态累加的 |
首先来看看 |
运算符,由于每个常量二进制的 1 都在不同位置,两个任意常量经过 |
运算符后,其实就是增加了 1 的位置
1 | NoContext | BatchedContext;// 000001 |
所以可以理解 |
运算符就是「记录新增 Context」或者说「状态的累加」。如果用传统的写法,可能是这样的
1 | const BatchedContext = 'BatchedContext'; |
判断状态是否存在的 &
然后是 &
运算符,同样在特定数字下它能表达特殊的含义。首先由于每个常量的 1 都在不同位置,所以两个常量直接 &
操作后结果肯定是 000000,但如果是经过 |
运算后的 executionContext
和常量进行 &
运算,结果就有意思了
1 | let executionContext = NoContext; |
可以发现它有「判断是否存在」的含义,但由于使用了 &=
所以更确切的含义是「如果存在就覆盖」。
移除状态的 ~
这个符号目前看到的场景是和 &
一起使用
1 | let executionContext = NoContext; |
前面说过 &=
是判断存在就覆盖的含义,但是 BatchedContext
多了个 ~
符号,含义肯定有所改变,那这段代码究竟是什么含义呢?
1 | executionContext = 000000 & ~000001; |
换个用例
1 | executionContext = NoContext; |
等同于
1 | executionContext = 000011 & ~000001; |
它表达的是「移除指定状态」,如果用传统的写法
1 | const BatchedContext = 'BatchedContext'; |
位运算的优点
为什么 react
使用这么难理解的方式去实现这些逻辑呢,大概是因为位运算的性能好很多吧,如果使用数组来保存状态,无论是要占用堆内存,还是在判断时还需要在内存中查询,任何方面,相比位运算都没有一点优势,那么采用位运算看来是必然的选择了。